﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading; // added
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Collections;

// socket example: simple chat by JDH
// - server keeps a list of clients
// - main thread is GUI
// - starts thread to listens
// - when message received sends each client the message (new connection)

// so more like unix, use Socket directly rather than TcpClient/TcpListener
// here connection oriented

// to simplify: no long-term connections, little error handling, etc. etc.

// messages (go by address):
//    connect name > response: name that will be used (will add # so unique) 
//    disconnect name
//    name message



namespace Server
{
    public partial class ServerForm : Form
    {
        #region MemberData

        // connection info
        private ArrayList m_aClientList;
        private int m_nSvrPort = 9050;
        private const int MAX_MESSAGE_SIZE = 1000;

        #endregion MemberData

        public ServerForm()
        {
            InitializeComponent();

            AddToList("Server started");

            // init client list
            m_aClientList = new ArrayList();

            // start thread to allow connects
            ThreadPool.QueueUserWorkItem(listenForConnectsAndMessages);
        }


        #region ListenForClients

        // Thread - always runs while application active
        private void listenForConnectsAndMessages(Object stateInfo) 
        {
            // set my "number": using whatever ip address I have and the specified port
            IPEndPoint cMyAddress = new IPEndPoint(IPAddress.Any, m_nSvrPort);
            Socket cMyTcpSocket = new Socket(AddressFamily.InterNetwork,
                                        SocketType.Stream, ProtocolType.Tcp);
            cMyTcpSocket.Bind(cMyAddress);
            
            String sText, sLowercase;
            int nAmountReceived;
            byte[] aDataBuffer;
            String sClientName;

            // listen messages (like turning on your phone)
            cMyTcpSocket.Listen(10); // allow a queue in case several

            // just keep listening...
            while (true)
            {
                // take connection from client
                // waits until a call comes (then like picking up the phone)
                Socket cClientSocket = cMyTcpSocket.Accept();
                IPEndPoint cClientAddress = (IPEndPoint)cClientSocket.RemoteEndPoint; // (look at caller number)

                // get the message
                aDataBuffer = new byte[MAX_MESSAGE_SIZE]; // reset buffer size
                nAmountReceived = cClientSocket.Receive(aDataBuffer); // no loop know short message
                if (nAmountReceived != 0)
                {
                    // get message as a string
                    sText = Encoding.ASCII.GetString(aDataBuffer, 0, nAmountReceived);
                    sLowercase = sText.ToLower();

                    // prepare message to pass to others
                    // handling depends on type of message
                    if (sLowercase.IndexOf("connect") == 0)
                    {
                        // new connection

                        // get unique name - add number as suffix
                        string sRequestedName = sText.Substring(8); // remove "connect "
                        bool bNameOkay;
                        int nCount = 1;
                        sClientName = sRequestedName.Replace(' ', '_'); // no spaces
                        do
                        {
                            bNameOkay = true;
                            if (clientWithNameExists(sClientName))
                            {
                                // name exists - add number to end and keep trying
                                nCount++;
                                sClientName = sRequestedName + nCount.ToString();
                                bNameOkay = false;
                            }
                        } while (!bNameOkay);

                        // create client
                        ClientInfo cClient;
                        cClient = new ClientInfo();
                        cClient.AddressSendTo = cClientAddress;
                        cClient.ID = sClientName;

                        // add to list
                        m_aClientList.Add(cClient);

                        // send response
                        aDataBuffer = Encoding.ASCII.GetBytes(sClientName);
                        cClientSocket.Send(aDataBuffer, aDataBuffer.Length, SocketFlags.None);

                        // inform others
                        sText = cClient.ID + " connected";

                        // display info in server
                        Invoke(new UpdateUIDelegate(AddToList), sText);
                    }
                    else if (sLowercase.IndexOf("disconnect") == 0)
                    {
                        // disconnecting

                        // get ID for client and REMOVE
                        sClientName = sText.Substring(11); // remove "disconnect"
                        if (clientFound(sClientName, cClientAddress, true))
                        {
                            // notify others
                            sText = sClientName + " disconnected";

                            // display info in server
                            Invoke(new UpdateUIDelegate(AddToList), sText);
                        }
                        else
                        {
                            sText = "";
                            Invoke(new UpdateUIDelegate(AddToList), "Invalid disconnect: " + sText);
                        }
                    }
                    else
                    {
                        // normal message between users
                        sClientName = sText.Substring(0, sText.IndexOf(' ')); // before first space
                        if (clientFound(sClientName, cClientAddress, false))
                        {
                            // add semincolon and notify others
                            sText = sText.Replace(" ", ": ");
                        }
                        else
                        {
                            sText = "";
                            Invoke(new UpdateUIDelegate(AddToList), "Invalid Message: " + sText);
                        }
                    }

                    // in all cases send message to all clients
                    if (sText != "")
                        ThreadPool.QueueUserWorkItem(SendMessageToClients, sText);
                }

                // clean-up from this exchange (like hanging up) 
                cClientSocket.Close();
            }

            // theoretically should do... but will never happen
            //cMyTcpSocket.Close();
        }

        private bool clientFound(string sClientName, IPEndPoint cAddress, bool bRemoveFromList)
        {
            bool result = false;

            foreach (ClientInfo cClient in m_aClientList)
            {
                if (cClient.AddressSendTo.Address.ToString() == cAddress.Address.ToString() &&
                    cClient.ID == sClientName)
                {
                    // if requested remove from list
                    if (bRemoveFromList)
                    {
                        m_aClientList.Remove(cClient);
                    }
                    result = true;
                    break;
                }
            }

            return result;
        }

        private bool clientWithNameExists(String cName)
        {
            foreach (ClientInfo cClient in m_aClientList)
            {
                if (cClient.ID == cName)
                {
                    // client with this name found - can stop
                    return true;
                }
            }

            // didn't find...
            return false;
        }


        // delegate tells what the function looks like (return, parameter)
        // must match method
        private delegate void UpdateUIDelegate(string sText);
        private void AddToList(string sMessage) // will be called by thread
        {
            tbDisplay.Text += DateTime.Now.ToString() + ": " + sMessage + "\r\n";
        }

        #endregion ListenForClients


        #region SendMessageToClients

        // Thread 2 - started to distribute a message to all clients (then ends)
        // since only for short time, ThreadPool better here
        private void SendMessageToClients(Object stateInfo) // runs in ThreadPool
        {
            // first parameter really string with message for clients to display
            string sMessage = Convert.ToString(stateInfo);

            // check if client needs update 
            foreach (ClientInfo cClient in m_aClientList) 
            {
                // inform client - sim to client
                Socket cClientSocket = new Socket(AddressFamily.InterNetwork,
                                             SocketType.Stream, ProtocolType.Tcp);
                try
                {
                    // connect to a specific end point
                    cClientSocket.Connect(cClient.AddressSendTo);
                }
                catch //(SocketException e)
                {
                    tbDisplay.Text += "Error: konnte nicht mit Client " + cClient.ID + " zum updaten verbinden " + "\r\n";
                    continue; // skip this and go to next
                }

                // send the message as bytes - short so length okay
                cClientSocket.Send(Encoding.ASCII.GetBytes(sMessage));

                // no response

                // clean-up
                cClientSocket.Shutdown(SocketShutdown.Both);
                cClientSocket.Close();
            }
        }
        
        #endregion SendMessageToClients

    }
}